package cn.datawin.spider.httputil;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.FileNameMap;
import java.net.URLConnection;
import java.nio.charset.Charset;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;

import org.apache.http.Header;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponseInterceptor;
import org.apache.http.StatusLine;
import org.apache.http.client.CookieStore;
import org.apache.http.client.config.AuthSchemes;
import org.apache.http.client.config.CookieSpecs;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.DeflateDecompressingEntity;
import org.apache.http.client.entity.GzipDecompressingEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.cookie.Cookie;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.FormBodyPart;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.entity.mime.content.StringBody;
import org.apache.http.impl.client.BasicCookieStore;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.impl.cookie.BasicClientCookie;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;

import cn.datawin.spider.httputil.HttpRequest.Method;
import cn.datawin.spider.util.IOUtil;

@SuppressWarnings("deprecation")
public class HttpClientService_3_2 implements HttpService {

	private CloseableHttpClient client;

	private PoolingHttpClientConnectionManager connectionManager;
	HttpClientContext localContext = HttpClientContext.create();
	HttpClientBuilder httpClientBuilder = HttpClients.custom();
	RequestConfig defaultRequestConfig = RequestConfig.custom()
    .setCookieSpec(CookieSpecs.BEST_MATCH)
    .setExpectContinueEnabled(true)
    .setStaleConnectionCheckEnabled(true)
    .setRedirectsEnabled(true).setRelativeRedirectsAllowed(true)
    .setTargetPreferredAuthSchemes(Arrays.asList(AuthSchemes.NTLM, AuthSchemes.DIGEST))
    .setProxyPreferredAuthSchemes(Arrays.asList(AuthSchemes.BASIC))
    .build();

	int state = 0;

	public HttpClientService_3_2() {
		Registry<ConnectionSocketFactory> reg = RegistryBuilder.<ConnectionSocketFactory> create().register("http", PlainConnectionSocketFactory.INSTANCE)
				.register("https", SSLConnectionSocketFactory.getSocketFactory()).build();
		connectionManager = new PoolingHttpClientConnectionManager(reg);
		connectionManager.setDefaultMaxPerRoute(100);
		connectionManager.setMaxTotal(100);
		SocketConfig socketConfig = SocketConfig.custom().setSoKeepAlive(true).setTcpNoDelay(true).build();
		httpClientBuilder.setDefaultSocketConfig(socketConfig);

		CookieStore cookieStore = new BasicCookieStore();
		localContext.setCookieStore(cookieStore);
		httpClientBuilder.setDefaultCookieStore(cookieStore);
		httpClientBuilder.setDefaultRequestConfig(defaultRequestConfig);

		client = httpClientBuilder.build();
	}

	public HttpResponse execute(HttpRequest req) throws Exception {
		long start = System.currentTimeMillis();
		HttpRequestBase requestBase = null;
		org.apache.http.HttpResponse httpResponse = null;
		try {
			requestBase = getHttpRequestBase(req);

			httpResponse = client.execute(requestBase, localContext);
			
			HttpHost curHost = (HttpHost) localContext.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
			HttpUriRequest curReq = (HttpUriRequest) localContext.getAttribute(ExecutionContext.HTTP_REQUEST);
			String curUrl = (curReq.getURI().isAbsolute()) ? curReq.getURI().toString() : (curHost.toURI() + curReq.getURI());
			req.setUrl(curUrl);

			StatusLine line = httpResponse.getStatusLine();

			HttpResponse res = new HttpResponse(line.getStatusCode(), line.getReasonPhrase());

			/* 返回头文件 */
			for (Header header : httpResponse.getAllHeaders()) {
				if ("Set-Cookie".equals(header.getName()) && res.getHeader("Set-Cookie") != null) {
					res.setHeader(header.getName(), res.getHeader("Set-Cookie") + "," + header.getValue());
					continue;
				}
				res.setHeader(header.getName(), header.getValue());
			}

			/* 返回cookies */
			for (Cookie co : localContext.getCookieStore().getCookies()) {
				res.setCookie(co.getName(), co.getValue());
			}

			ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

			InputStream in = httpResponse.getEntity().getContent();
			IOUtil.copy(in, outputStream);

			/* 返回流 byte[] ,返回流 */
			res.setCharset(req.getCharset());
			res.setResponseData(outputStream.toByteArray());
			return res;
		} catch (Exception e) {
			System.out.println("HttpClientService_3_2==" + req.getUrl());
			throw e;
		} finally {
			if(requestBase!=null){
		    	requestBase.reset();
		    	requestBase = null;
		    }
			httpResponse = null;
			long l = (System.currentTimeMillis() - start);
			System.out.println(l + "::URL::" + req.getUrl() + "::" + req.getPostParams());
		}
	}

	public void bindInterceptor() {
		httpClientBuilder.addInterceptorFirst(new HttpRequestInterceptor() {

			public void process(final org.apache.http.HttpRequest request, final HttpContext context) throws HttpException, IOException {
				if (!request.containsHeader("Accept-Encoding")) {
					request.addHeader("Accept-Encoding", "gzip");
				}

			}
		}).addInterceptorLast(new HttpResponseInterceptor() {
			@Override
			public void process(org.apache.http.HttpResponse resp, HttpContext arg1) throws HttpException, IOException {
				Header[] headers = resp.getHeaders("Content-Encoding");
				for (Header header : headers) {
					if ("gzip".equals(header.getValue())) {
						resp.setEntity(new GzipDecompressingEntity(resp.getEntity()));
						return;
					} else if ("deflate".equals(header.getValue())) {
						resp.setEntity(new DeflateDecompressingEntity(resp.getEntity()));
						return;
					}
				}

			}
		});

	}

	/**
	 * 解析httpRequest
	 * 
	 * @param req
	 * @return
	 */
	public HttpRequestBase getHttpRequestBase(HttpRequest req) {
		HttpRequestBase requestBase = null;
		if (req.getMethod() == Method.post) {
			requestBase = new HttpPost(req.getUrl());

			if (req.getFileMap().size() > 0) {
				parseMartipart(requestBase, req);
			} else if (req.getPostParams().size() > 0) {
				parsePostParams(requestBase, req);
			}
		} else {
			if (req.getPostParams().size()>0) {
				String spe = req.getUrl().indexOf("?") != -1 ? "&" : "?";
				requestBase = new HttpGet(req.getUrl() + spe + req.postParamsToString());
			}else{
				requestBase = new HttpGet(req.getUrl());
			}
		}

		RequestConfig requestConfig = RequestConfig.custom().setSocketTimeout(req.getReadTimeout() + req.getConnectTimeout()).setConnectTimeout(req.getConnectTimeout())
				.setConnectionRequestTimeout(req.getReadTimeout()).build();
		requestBase.setConfig(requestConfig);

		parseHeader(requestBase, req);
		parseCookies(requestBase, req);

		return requestBase;
	}

	/**
	 * 解析头文件
	 * 
	 * @param requestBase
	 * @param req
	 */
	public void parseHeader(HttpRequestBase requestBase, HttpRequest req) {
		Map<String, String> headers = req.getHeaders();
		for (String key : headers.keySet()) {
			requestBase.setHeader(key, headers.get(key));
		}
	}

	/**
	 * 解析cookie
	 * 
	 * @param requestBase
	 * @param req
	 */
	public void parseCookies(HttpRequestBase requestBase, HttpRequest req) {
		Map<String, String> cookies = req.getCookies();
		for (String key : cookies.keySet()) {
			localContext.getCookieStore().addCookie(new BasicClientCookie(key, cookies.get(key)));
		}
	}

	/**
	 * 解析提交参数
	 * 
	 * @param requestBase
	 * @param req
	 */
	public void parsePostParams(HttpRequestBase requestBase, HttpRequest req) {
		ContentType contentType = ContentType.create("application/x-www-form-urlencoded", Charset.forName(req.getCharset()));
		StringEntity entity = new StringEntity(req.postParamsToString(), contentType);
		((HttpPost) requestBase).setEntity(entity);
	}

	/**
	 * 解析文件流
	 * 
	 * @param requestBase
	 * @param req
	 */
	public void parseMartipart(HttpRequestBase requestBase, HttpRequest req) {
		MultipartEntity entity = new MultipartEntity();
		String charset = req.getCharset();

		Map<String, String> postMap = req.getPostParams();
		for (String key : postMap.keySet()) {
			String value = postMap.get(key);
			value = value == null ? "" : value;
			try {
				entity.addPart(key, new StringBody(value, Charset.forName(charset)));
			} catch (UnsupportedEncodingException e) {
				e.printStackTrace();
			}
		}

		Map<String, File> fileMap = req.getFileMap();
		for (String key : fileMap.keySet()) {
			File value = fileMap.get(key);
			entity.addPart(new FormBodyPart(key, new FileBody(value, getMimeType(value))));
		}
		((HttpPost) requestBase).setEntity(entity);
	}

	private String getMimeType(File file) {
		FileNameMap fileNameMap = URLConnection.getFileNameMap();
		return fileNameMap.getContentTypeFor(file.toString());
	}

	public static ClientConnectionManager initSSH() {
		try {
			ClientConnectionManager ccm = new ThreadSafeClientConnManager();
			SSLContext sslcontext = SSLContext.getInstance("TLS");
			sslcontext.init(null, new TrustManager[] { new X509TrustManager() }, null);
			SSLSocketFactory ssf = new SSLSocketFactory(sslcontext);
			SchemeRegistry sr = ccm.getSchemeRegistry();
			sr.register(new Scheme("https", 443, ssf));
			return ccm;
		} catch (Exception e) {
			return null;
		}
	}

	public static class X509TrustManager implements javax.net.ssl.X509TrustManager {
		public void checkClientTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
		}

		public void checkServerTrusted(X509Certificate[] arg0, String arg1) throws CertificateException {
		}

		public X509Certificate[] getAcceptedIssuers() {
			return null;
		}
	}

	@Override
	public void shutdown() {
		try {
			client.close();
			this.state =1;
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	@Override
	public boolean isShutdown() {
		if (this.state == 1)
			return true;
		return false;
	}

}
